/*
 * Copyright 2012 Andrea Parodi
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package eban.storebyrules;

import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;
import org.testng.collections.Lists;

import java.lang.ref.WeakReference;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Author: parroit
 * Created: 25/05/12 20.21
 */
public final class PersistedItemWeakList {
    private static Object lock = new Object();
    private static PersistedItemWeakList ourInstance;
    static {
        ourInstance = new PersistedItemWeakList();
       ourInstance.cleaner.start();
    }
    public static PersistedItemWeakList getInstance() {
        return ourInstance;
    }

    private PersistedItemWeakList() {
    }

    private final Object disposingLock = new Object();


    private final ListCleaner cleaner = new ListCleaner();
    private boolean pleaseDispose;

    public void dispose() {
        synchronized (disposingLock) {
            pleaseDispose = true;
        }
    }

    private final Map<ByClassKey, WeakReference<Object>> persistedLiveObjects = Maps.newHashMap();

    public void add(Object key, Object instance) {
        Preconditions.checkNotNull(key);
        Preconditions.checkNotNull(instance);
        synchronized (persistedLiveObjects) {
            persistedLiveObjects.put(new ByClassKey(key, instance.getClass()), new WeakReference<Object>(instance));
        }

    }

    public void detach(Object key, Class<?> clazz) {
        synchronized (persistedLiveObjects) {

            ByClassKey classKey = new ByClassKey(key, clazz);
            persistedLiveObjects.remove(classKey);

        }
    }

    final static class ByClassKey {
        private final Object key;
        private final Class<?> clazz;

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            ByClassKey that = (ByClassKey) o;

            if (!clazz.equals(that.clazz)) return false;
            if (!key.equals(that.key)) return false;

            return true;
        }

        @Override
        public int hashCode() {

            int result = key.hashCode();
            result = 31 * result + clazz.hashCode();
            return result;
        }

        @Override public String toString() {
            return String.format("ByClassKey{%s, %s}", key, clazz.getSimpleName());
        }

        private ByClassKey(Object key, Class<?> clazz) {
            Preconditions.checkNotNull(key, "key param");
            Preconditions.checkNotNull(clazz, "clazz param");
            this.key = key;
            this.clazz = clazz;
        }
    }

    public boolean isPersistent(Object key, Class<?> clazz) {
        synchronized (persistedLiveObjects) {
            WeakReference<Object> reference = persistedLiveObjects.get(new ByClassKey(key, clazz));
            return reference != null && reference.get() != null;
        }
    }

    private class ListCleaner extends Thread {
        @Override public void run() {


            while (!shouldDispose()) {
                Set<Map.Entry<ByClassKey, WeakReference<Object>>> entries = safeMapEntries();
                List<Object> toClear = getGarbageCollectedInstances(entries);
                clearList(toClear);
                if (sleepForAWhile()) return;
            }
        }

        private boolean sleepForAWhile() {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
                return true;
            }
            return false;
        }

        private void clearList(List<Object> toClear) {
            if (toClear.size() > 0) {
                synchronized (persistedLiveObjects) {
                    for (Object key : toClear) {
                        persistedLiveObjects.remove(key);
                    }
                }
            }
        }

        private List<Object> getGarbageCollectedInstances(Set<Map.Entry<ByClassKey, WeakReference<Object>>> entries) {
            List<Object> toClear = Lists.newArrayList();
            for (Map.Entry<ByClassKey, WeakReference<Object>> entry : entries) {
                if (entry.getValue().get() == null) {
                    toClear.add(entry.getKey());
                }
            }
            return toClear;
        }

        private Set<Map.Entry<ByClassKey, WeakReference<Object>>> safeMapEntries() {
            Set<Map.Entry<ByClassKey, WeakReference<Object>>> entries;

            synchronized (persistedLiveObjects) {
                entries = Maps.newHashMap(persistedLiveObjects).entrySet();
            }
            return entries;
        }
    }

    private boolean shouldDispose() {
        synchronized (disposingLock) {
            return pleaseDispose;
        }
    }


}
